#include <atomic>
#include "fiber.h"
#include "config.h"
#include "macro.h"
#include "log.h"
#include "scheduler.h"

namespace sylar {

static Logger::ptr g_logger = SYLAR_LOG_NAME("system");

// 使用原子量生成协程 id
static std::atomic<uint64_t> s_fiber_id {0};
// 使用原子量记录协程数量
static std::atomic<uint64_t> s_fiber_count {0};

// 线程局部变量，用于线程中返回协程
static thread_local Fiber* t_fiber = nullptr;
// 线程局部变量，main 协程
static thread_local Fiber::ptr t_threadFiber = nullptr;

// 通过配置文件获取协程默认栈大小，默认为 1024 * 1024
static ConfigVar<uint32_t>::ptr g_fiber_stack_size =
    Config::Lookup<uint32_t>("fiber.stack_size", 1024 * 1024, "fiber stack size");

// 协程栈内存分配器，使用 malloc 作为底层实现
class MallocStackAllocator {
public:
  static void* Alloc(size_t size) {
    return malloc(size);
  }

  static void Dealloc(void* vp, size_t size) {
    return free(vp);
  }
};

// 栈内存分配器的别名
using StackAllocator = MallocStackAllocator;

// 将当前线程构造为协程，用于构造第一个协程
Fiber::Fiber() {
  ++s_fiber_count;
  m_state = EXEC;
  SetThis(this);

  // 获取上下文
  if(getcontext(&m_ctx)) {
    SYLAR_ASSERT_INFO(false, "getcontext");
  }

  SYLAR_LOG_DEBUG(g_logger) << "Fiber::Fiber";
}

// 协程的构造函数
Fiber::Fiber(std::function<void()> cb, size_t stacksize, bool use_caller)
  :m_id(++s_fiber_id)
  ,m_cb(cb) {
  ++s_fiber_count;
  // 获取栈大小
  m_stacksize = stacksize ? stacksize : g_fiber_stack_size->getValue();

  // 分配栈内存
  m_stack = StackAllocator::Alloc(m_stacksize);
  // 获取上下文
  if(getcontext(&m_ctx)) {
    SYLAR_ASSERT_INFO(false, "getcontext");
  }
  // 上下文关联的上下文
  m_ctx.uc_link = nullptr;
  // 上下文的栈地址
  m_ctx.uc_stack.ss_sp = m_stack;
  // 上下文的栈大小
  m_ctx.uc_stack.ss_size = m_stacksize;

  // 创建一个新的上下文
  if (!use_caller) {
    makecontext(&m_ctx, &Fiber::MainFunc, 0);
  } else {
    makecontext(&m_ctx, &Fiber::CallerMainFunc, 0);
  }

  SYLAR_LOG_DEBUG(g_logger) << "Fiber::Fiber id=" << m_id;
}

Fiber::~Fiber() {
  --s_fiber_count;
  if(m_stack) { // 有栈，不是主协程
    SYLAR_ASSERT(m_state == TERM
        || m_state == EXCEPT
        || m_state == INIT);

    StackAllocator::Dealloc(m_stack, m_stacksize);
  } else { // 主协程
    SYLAR_ASSERT(!m_cb);
    SYLAR_ASSERT(m_state == EXEC);

    Fiber* cur = t_fiber;
    if(cur == this) {
      SetThis(nullptr);
    }
  }

  SYLAR_LOG_DEBUG(g_logger) << "Fiber::~Fiber id=" << m_id;
}

// 重置协程函数，并重置状态
// INIT，TERM
void Fiber::reset(std::function<void()> cb) {
  // 只有我们创建的协程才能调用 reset
  SYLAR_ASSERT(m_stack);
  // 只有当协程已经终止或者异常或者是刚初始化的时候才能 reset
  SYLAR_ASSERT(m_state == TERM
      || m_state == EXCEPT
      || m_state == INIT);
  m_cb = cb;
  // 获取上下文
  if (getcontext(&m_ctx)) {
    SYLAR_ASSERT_INFO(false, "getcontext");
  }
  // 上下文关联的上下文
  m_ctx.uc_link = nullptr;
  // 上下文的栈地址
  m_ctx.uc_stack.ss_sp = m_stack;
  // 上下文的栈大小
  m_ctx.uc_stack.ss_size = m_stacksize;
  // 创建一个新的上下文
  makecontext(&m_ctx, &Fiber::MainFunc, 0);

  // 将状态设置为 INIT
  m_state = INIT;
}

void Fiber::call() {
  SetThis(this);
  m_state = EXEC;
  SYLAR_LOG_ERROR(g_logger) << getId();
  if (swapcontext(&t_threadFiber->m_ctx, &m_ctx)) {
    SYLAR_ASSERT_INFO(false, "swapcontext");
  }
}

void Fiber::back() {
  SetThis(t_threadFiber.get());
  if (swapcontext(&m_ctx, &t_threadFiber->m_ctx)) {
    SYLAR_ASSERT_INFO(false, "swapcontext");
  }
}

// 切换到当前协程执行
void Fiber::swapIn() {
  SetThis(this);
  // 只有当 this 的状态不为 EXEC 时才能切换
  SYLAR_ASSERT(m_state != EXEC);
  m_state = EXEC;

  // 切换上下文
  if (swapcontext(&Scheduler::GetMainFiber()->m_ctx, &m_ctx)) {
    SYLAR_ASSERT_INFO(false, "swapcontext");
  }
}

// 切换到后台执行
void Fiber::swapOut() {
  // 将正在运行的协程切换为主协程
  SetThis(Scheduler::GetMainFiber());

  //切换上下文
  if(swapcontext(&m_ctx, &Scheduler::GetMainFiber()->m_ctx)) {
    SYLAR_ASSERT_INFO(false, "swapcontext");
  }
}

// 设置当前协程
void Fiber::SetThis(Fiber* f) {
  // 将正在运行的线程切换为 f
  t_fiber = f;
}

// 获取当前协程
Fiber::ptr Fiber::GetThis() {
  // 当前协程存在
  if (t_fiber) {
    return t_fiber->shared_from_this();
  }
  // 创建主协程，并返回
  Fiber::ptr main_fiber(new Fiber);
  // 当前正在运行的协程是主协程
  SYLAR_ASSERT(t_fiber == main_fiber.get());
  // 将主协程设置为 main_fiber
  t_threadFiber = main_fiber;
  return t_fiber->shared_from_this();
}

// 获取协程 id
uint64_t Fiber::GetFiberId() {
  // 当前协程存在
  if (t_fiber) {
    return t_fiber->getId();
  }
  return 0;
}

// 协程切换到后台，并且设置为Ready状态
void Fiber::YieldToReady() {
  Fiber::ptr cur = GetThis();
  cur->m_state = READY;
  cur->swapOut();
}

// 协程切换到后台，并且设置为Hold状态
void Fiber::YieldToHold() {
  Fiber::ptr cur = GetThis();
  cur->m_state = HOLD;
  cur->swapOut();
}

//总协程数
uint64_t Fiber::TotalFibers() {
  return s_fiber_count;
}

void Fiber::MainFunc() {
  Fiber::ptr cur = GetThis();
  SYLAR_ASSERT(cur);
  try {
    // 执行回调函数
    cur->m_cb();
    // 执行结束将函数清空
    cur->m_cb = nullptr;
    // 将状态置为结束
    cur->m_state = TERM;
  } catch (std::exception& ex) {
    cur->m_state = EXCEPT;
    SYLAR_LOG_ERROR(g_logger) << "Fiber Except: " << ex.what()
      << " fiber_id=" << cur->getId() << std::endl
      << sylar::BacktraceToString();
  } catch (...) {
    cur->m_state = EXCEPT;
    SYLAR_LOG_ERROR(g_logger) << "Fiber Except: "
      << " fiber_id=" << cur->getId() << std::endl
      << sylar::BacktraceToString();
  }

  auto raw_ptr = cur.get();
  // 减少 cur 的引用计数
  cur.reset();
  // 执行结束切换出去
  raw_ptr->swapOut();

  SYLAR_ASSERT_INFO(false, "Fiber::MainFunc never reach fiber_id=" + std::to_string(raw_ptr->getId()));
}

void Fiber::CallerMainFunc() {
  Fiber::ptr cur = GetThis();
  SYLAR_ASSERT(cur);
  try {
    // 执行回调函数
    cur->m_cb();
    // 执行结束将函数清空
    cur->m_cb = nullptr;
    // 将状态置为结束
    cur->m_state = TERM;
  } catch (std::exception& ex) {
    cur->m_state = EXCEPT;
    SYLAR_LOG_ERROR(g_logger) << "Fiber Except: " << ex.what()
      << " fiber_id=" << cur->getId() << std::endl
      << sylar::BacktraceToString();
  } catch (...) {
    cur->m_state = EXCEPT;
    SYLAR_LOG_ERROR(g_logger) << "Fiber Except: "
      << " fiber_id=" << cur->getId() << std::endl
      << sylar::BacktraceToString();
  }

  auto raw_ptr = cur.get();
  // 减少 cur 的引用计数
  cur.reset();
  // 执行结束切换出去
  raw_ptr->back();

  SYLAR_ASSERT_INFO(false, "Fiber::CallerMainFunc never reach fiber_id=" + std::to_string(raw_ptr->getId()));
}

}
